

/***
 * @Date            : 2024-12-28 22:02:54
 * @Author          : AnimateX(Baron)
 * @LastEditors     : AnimateX(Baron)
 * @LastEditTime    : 2024-12-28 22:03:03
 * @FilePath        : /include/ringbuffer.h
 * @CopyRight       : Copyright (c) 2024 RockChip Technology Co. Ltd. All rights reserved.
 * @Description     : RingBuffer simplify version.
 */
#pragma once

#include "common_log.h"
#include <vector>
#include <chrono>
#include <cstddef>
#include <cstring>
#include <memory>
#include <mutex>
#include <condition_variable>


namespace rc {
    template<typename T>
    class RingBuffer {
    private:
        std::vector<std::unique_ptr<T[]>> buffers_;
        size_t size_ = 0;   // buffer size
        size_t head_ = 0;   // write locate
        size_t tail_ = 0;   // read locate
        mutable std::mutex mutex_;
        std::condition_variable not_full_;
        std::condition_variable not_empty_;
        size_t imageSize_;

    public:
        RingBuffer(size_t capacity, size_t imageSize)
            :  imageSize_(imageSize) {
            buffers_.resize(capacity);
            for (auto& buffer : buffers_) {
                buffer = std::make_unique<T[]>(imageSize);
            }
        }

        inline bool tryPush(const T* data, std::chrono::milliseconds timeout) {
            RCLogger& logger = RCLogger::getInstance();
            if (!data) {
                logger.rs_error(" (tryPush) Input data is empty.");
                return false;
            }

            std::unique_lock<std::mutex> lock(mutex_);
            if (!not_full_.wait_for(lock, timeout, [this] { return size_ < buffers_.size(); })) {
                logger.rs_error(" (tryPush) Ring buffer is full, push data failed.");
                return false;  // timeout
            }

            std::memcpy(buffers_[head_].get(), data, imageSize_ * sizeof(T));
            head_ = (head_ + 1) % buffers_.size();
            ++size_;

            lock.unlock();
            not_empty_.notify_one();
            return true;
        }


        inline bool tryPop(T* out_data, std::chrono::milliseconds timeout) {
            RCLogger& logger = RCLogger::getInstance();
            if (!out_data) {
                logger.rs_error(" (tryPop) output data is empty.");
                return false;
            }

            std::unique_lock<std::mutex> lock(mutex_);
            if (!not_empty_.wait_for(lock, timeout, [this] { return size_ > 0; })) {
                logger.rs_error(" (tryPop) Ring buffer is full, pop data failed.");
                return false;
            }

            std::memcpy(out_data, buffers_[tail_].get(), imageSize_ * sizeof(T));
            tail_ = (tail_ + 1) % buffers_.size();
            --size_;

            lock.unlock();
            not_full_.notify_one();
            return true;
        }

        constexpr size_t capacity() const noexcept {
            return buffers_.size();
        }

        void clear() {
            std::lock_guard<std::mutex> lock(mutex_);
            size_ = 0;
            head_ = 0;
            tail_ = 0;
        }

        size_t size() const {
            std::lock_guard<std::mutex> lock(mutex_);
            return size_;
        };

        bool empty() const {
            std::lock_guard<std::mutex> lock(mutex_);
            return size_ == 0;
        };

        bool full() const {
            std::lock_guard<std::mutex> lock(mutex_);
            return size_ == buffers_.size();
        }
    };
}
